package bancosys.tec.utils.date;

import java.util.Calendar;
import java.util.Locale;

import jmine.tec.utils.UtilsMessages;

import org.apache.commons.lang.time.DateFormatUtils;

import bancosys.tec.exception.LocalizedMessageHolder;

/**
 * @created Dec 4, 2006
 * @author Gustavo Almeida
 */
public class Date extends java.sql.Date implements Cloneable, LocalizedMessageHolder {
    private static final int MIDDAY = 12;

    private transient Calendar calendar;

    /** Valor de data utilizado em todo o sistema como "infinito". */
    public static final Date MAX_VALUE = new Date(2999, 12, 31);

    public static final Date MIN_VALUE = new Date(1970, 1, 1);

    /**
     * Construtor.
     * 
     * @param date long representando a data.
     */
    public Date(long date) {
        super(date);
        this.getCalendar().setTimeInMillis(date);
        this.updateTime();
    }

    /**
     * @deprecated deve ser usado apenas durante a transição de Calendar para Date. Construtor.
     * @param date o calendar.
     */
    @Deprecated
    public Date(Calendar date) {
        this(date.getTimeInMillis());
    }

    /**
     * Construtor default.
     */
    public Date() {
        this(new java.util.Date().getTime());
    }

    /**
     * Constroi a partir de um outro date.
     * 
     * @param date um date.
     */
    public Date(java.util.Date date) {
        this(date.getTime());
    }

    /**
     * Construtor a partir do dia/mês/ano.
     * 
     * @param year o ano.
     * @param month o mês.
     * @param day o dia.
     */
    public Date(int year, int month, int day) {
        super(0);
        Calendar cal = this.getCalendar();
        cal.set(year, month - 1, day);
        this.updateTime();
    }

    /**
     * Construtor a partir do dia/mês/ano, podendo indicar se a data é lenient ou não.
     * 
     * @param year o ano.
     * @param month o mês.
     * @param day o dia.
     * @param lenient Flag para indicar se deve soltar excecao para datas invalidas.
     */
    public Date(int year, int month, int day, boolean lenient) {
        super(0);
        Calendar cal = this.getCalendar();
        cal.setLenient(lenient);
        cal.set(year, month - 1, day);
        this.updateTime();
    }

    /**
     * Adiciona os dias. <br>
     * Cuidado: altera a data corrente.
     * 
     * @param amount quantidade de dias a serem adicionados.
     * @return Uma nova data com dias adicionados.
     */
    public Date addDays(int amount) {
        return this.addField(Calendar.DATE, amount);
    }

    /**
     * Adiciona os meses. <br>
     * Cuidado: altera a data corrente.
     * 
     * @param amount quantidade de meses a serem adicionados.
     * @return Uma nova data com meses adicionados.
     */
    public Date addMonth(int amount) {
        return this.addField(Calendar.MONTH, amount);
    }

    /**
     * Adiciona os anos. <br>
     * Cuidado: altera a data corrente.
     * 
     * @param amount quantidade de anos a serem adicionados.
     * @return Uma nova data com anos adicionados.
     */
    public Date addYears(int amount) {
        return this.addField(Calendar.YEAR, amount);
    }

    /**
     * Retorna o dia da data.
     * 
     * @return o dia da data.
     */
    @Override
    public int getDay() {
        return this.getCalendar().get(Calendar.DAY_OF_MONTH);
    }

    /**
     * Retorna o mês da data.
     * 
     * @return o mês da data.
     */
    @Override
    public int getMonth() {
        // Soma 1 ao mes (entre 1 e 12)
        return this.getCalendar().get(Calendar.MONTH) + 1;
    }

    /**
     * Retorna o ano da data.
     * 
     * @return o ano da data.
     */
    @Override
    public int getYear() {
        return this.getCalendar().get(Calendar.YEAR);
    }

    /**
     * Clona a data a partir da instância.
     * 
     * @return a data clonada.
     */
    @Override
    public Object clone() {
        return new Date(this.getTime());
    }

    /**
     * Devolve a um calendar representando essa data, método provisório deve ser usado apensa durante a migração de Calendar para Date.
     * 
     * @deprecated
     * @return um calendar representando essa data
     */
    @Deprecated
    public Calendar asCalendar() {
        Calendar ret = Calendar.getInstance();
        ret.setTime(this);
        return ret;
    }

    /**
     * Retorna a data como calendar.
     * 
     * @return a data como calendar.
     */
    private Calendar getCalendar() {
        if (this.calendar == null) {
            this.calendar = Calendar.getInstance();
        }
        return this.calendar;
    }

    /**
     * Método acessório para manipular campos da instância de data corrente.
     * 
     * @param field a representação de campo no Calendar.
     * @param amount a quantidade a ser adicionada no campo da data.
     * @return A instância de data corrente, com os campos adicionados.
     */
    private Date addField(int field, int amount) {
        Calendar cal = this.getAndSetCalendar();
        cal.add(field, amount);
        this.updateTime();
        return this;
    }

    /**
     * Retorna o calendar da instância com a data setada.
     * 
     * @return o calendar da instância com a data setada.
     */
    private Calendar getAndSetCalendar() {
        Calendar cal = this.getCalendar();
        cal.setTime(this);
        return cal;
    }

    /**
     * Zera logicamente os campos de hora/minuto e segundo.
     */
    private void updateTime() {
        Calendar cal = this.getCalendar();
        cal.set(Calendar.HOUR_OF_DAY, MIDDAY);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        this.setTime(cal.getTimeInMillis());
    }

    /**
     * {@inheritDoc}
     */
    public String getMessage() {
        return this.getMessage(Locale.getDefault());
    }

    /**
     * {@inheritDoc}
     */
    public String getMessage(Locale locale) {
        String pattern = UtilsMessages.DATE_FORMAT.create().getMessage(locale);
        return DateFormatUtils.format(this, pattern, locale);
    }
}